package com.ruoyi.common.utils.date;

import cn.hutool.core.date.DateUtil;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @author xumh
 * @title: WorkdayUtils
 * @projectName ruoyi
 * @description: TODO
 * @date 2024/12/1216:09
 */
public class WorkdayUtils {
    private static final int START_YEAR = 2022;
    private static final int END_YEAR = 2022;
    private static final WorkdayUtils.BoundaryDateHandlingStrategy START_DATE_HANDLING_STRATEGY = (date) -> {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        return calendar.get(11) < 12;
    };
    private static final WorkdayUtils.BoundaryDateHandlingStrategy END_DATE_HANDLING_STRATEGY = (date) -> {
        return true;
    };
    private static final Map<Integer, Boolean> WORKDAY_MAP = new HashMap();
    private static final WorkdayUtils.SegmentTree SEGMENT_TREE;

    public WorkdayUtils() {
    }

    public static int howManyDays() {
        Calendar a = Calendar.getInstance();
        a.set(5, 1);
        a.roll(5, -1);
        int day = a.get(5);
        return day;
    }

    public static int howManyWorkday(Date startDate, Date endDate) {
        if (startDate.after(endDate)) {
            return howManyWorkday(endDate, startDate);
        } else {
            Calendar startCalendar = Calendar.getInstance();
            startCalendar.setTime(startDate);
            int startDays = getDaysAfterStartYear(startCalendar) - 1;
            Calendar endCalendar = Calendar.getInstance();
            endCalendar.setTime(endDate);
            int endDays = getDaysAfterStartYear(endCalendar) - 1;
            if (startDays == endDays) {
                return isWorkDay(startDate) ? 1 : 0;
            } else {
                if (!START_DATE_HANDLING_STRATEGY.ifCountAsOneDay(startDate)) {
                    ++startDays;
                }

                if (!END_DATE_HANDLING_STRATEGY.ifCountAsOneDay(endDate)) {
                    --endDays;
                }

                return SEGMENT_TREE.query(startDays, endDays);
            }
        }
    }

    public static boolean isWorkDay(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        int days = getDaysAfterStartYear(calendar) - 1;
        return SEGMENT_TREE.query(days, days) == 1;
    }

    private static int getDaysAfterStartYear(Calendar calendar) {
        int year = calendar.get(1);
        if (year >= 2022 && year <= 2022) {
            int days = 0;

            for(int i = 2022; i < year; ++i) {
                days += getDaysOfYear(i);
            }

            days += calendar.get(6);
            return days;
        } else {
            throw new IllegalArgumentException(String.format("系统目前仅支持计算%d年至%d年之间的工作日，无法计算%d年！", 2022, 2022, year));
        }
    }

    private static int getDaysOfYear(int year) {
        return (year % 4 != 0 || year % 100 == 0) && year % 400 != 0 ? 365 : 366;
    }

    private static void initWorkday() {
        WORKDAY_MAP.put(20170102, false);
        WORKDAY_MAP.put(20170122, true);
        WORKDAY_MAP.put(20170127, false);
        WORKDAY_MAP.put(20170130, false);
        WORKDAY_MAP.put(20170131, false);
        WORKDAY_MAP.put(20170201, false);
        WORKDAY_MAP.put(20170202, false);
        WORKDAY_MAP.put(20170204, true);
        WORKDAY_MAP.put(20170401, true);
        WORKDAY_MAP.put(20170403, false);
        WORKDAY_MAP.put(20170404, false);
        WORKDAY_MAP.put(20170501, false);
        WORKDAY_MAP.put(20170527, true);
        WORKDAY_MAP.put(20170529, false);
        WORKDAY_MAP.put(20170530, false);
        WORKDAY_MAP.put(20170930, true);
        WORKDAY_MAP.put(20171002, false);
        WORKDAY_MAP.put(20180101, false);
        WORKDAY_MAP.put(20180211, true);
        WORKDAY_MAP.put(20180215, false);
        WORKDAY_MAP.put(20180216, false);
        WORKDAY_MAP.put(20180219, false);
        WORKDAY_MAP.put(20180220, false);
        WORKDAY_MAP.put(20180221, false);
        WORKDAY_MAP.put(20180224, true);
        WORKDAY_MAP.put(20180405, false);
        WORKDAY_MAP.put(20180406, false);
        WORKDAY_MAP.put(20180408, true);
        WORKDAY_MAP.put(20180428, true);
        WORKDAY_MAP.put(20180430, false);
        WORKDAY_MAP.put(20180501, false);
        WORKDAY_MAP.put(20180618, false);
        WORKDAY_MAP.put(20180924, false);
        WORKDAY_MAP.put(20180929, true);
        WORKDAY_MAP.put(20180930, true);
        WORKDAY_MAP.put(20181001, false);
        WORKDAY_MAP.put(20181002, false);
        WORKDAY_MAP.put(20181003, false);
        WORKDAY_MAP.put(20181004, false);
        WORKDAY_MAP.put(20181005, false);
    }

    public static void main(String[] args) throws ParseException {
        System.out.println("测试开始：-------------------");
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date start = DateUtil.parse("2022-03-01 00:00:00");
        Date end = DateUtil.parse("2022-03-31 23:59:59");
        System.out.println(String.format("%s 到 %s, 有%d个工作日！", df.format(start), df.format(end), howManyWorkday(start, end)));
        Date now = new Date();
        Long startTs = CalendarMeasureUtil.getMonthStartTime(now.getTime(), (String)null);
        Long endTs = CalendarMeasureUtil.getMonthEndTime(now.getTime(), (String)null);
        start = new Date(startTs);
        end = new Date(endTs);
        System.out.println(String.format("本月 %s 到 %s, 有%d个工作日！", df.format(start), df.format(end), howManyWorkday(start, end)));
    }

    static {
        int totalDays = 0;

        for(int year = 2022; year <= 2022; ++year) {
            totalDays += getDaysOfYear(year);
        }

        int[] workdayArray = new int[totalDays];
        Calendar calendar = new GregorianCalendar(2022, 0, 1);

        for(int i = 0; i < totalDays; ++i) {
            int datestamp = calendar.get(1) * 10000 + (calendar.get(2) + 1) * 100 + calendar.get(5);
            Boolean isWorkDay = (Boolean)WORKDAY_MAP.get(datestamp);
            if (isWorkDay != null) {
                workdayArray[i] = isWorkDay ? 1 : 0;
            } else {
                int dayOfWeek = calendar.get(7);
                workdayArray[i] = dayOfWeek != 7 && dayOfWeek != 1 ? 1 : 0;
            }

            calendar.add(6, 1);
        }

        SEGMENT_TREE = new WorkdayUtils.SegmentTree(workdayArray);
    }

    private static class SegmentTree {
        private int[] data;
        private int numOfLeaf;

        public SegmentTree(int[] srcData) {
            for(this.numOfLeaf = 1; this.numOfLeaf < srcData.length; this.numOfLeaf <<= 1) {
            }

            this.data = new int[this.numOfLeaf << 1];

            int i;
            for(i = 0; i < srcData.length; ++i) {
                this.data[i + this.numOfLeaf] = srcData[i];
            }

            for(i = this.numOfLeaf - 1; i > 0; --i) {
                this.data[i] = this.data[i << 1] + this.data[i << 1 | 1];
            }

        }

        public int query(int left, int right) {
            if (left > right) {
                return this.query(right, left);
            } else {
                left = left + this.numOfLeaf - 1;
                right = right + this.numOfLeaf + 1;

                int sum;
                for(sum = 0; (left ^ right ^ 1) != 0; right >>= 1) {
                    if ((~left & 1) == 1) {
                        sum += this.data[left ^ 1];
                    }

                    if ((right & 1) == 1) {
                        sum += this.data[right ^ 1];
                    }

                    left >>= 1;
                }

                return sum;
            }
        }
    }

    private interface BoundaryDateHandlingStrategy {
        boolean ifCountAsOneDay(Date date);
    }
}
